# -*- coding: utf-8 -*-

from __future__ import division

__copyright__ = "Copyright (C) 2015 Dong Zhuang"

__license__ = """
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""


import django.forms as forms
from django.forms import ValidationError as FormValidationError
from django.utils.translation import ugettext as _, string_concat, ugettext_lazy
from django.conf import settings

from course.page.base import (
        PageBaseWithTitle, PageBaseWithValue, PageBaseWithHumanTextFeedback,
        PageBaseWithCorrectAnswer, AnswerFeedback, get_auto_feedback,
        markup_to_html)
from course.validation import ValidationError
from course.constants import participation_role
from course.content import get_repo_blob
from course.page.upload import FileUploadForm, FileUploadQuestion as FUQ
#{{{ mine customized

from relate.utils import StyledForm, dict_to_struct

from crispy_forms.layout import Layout, Field

from yaml import load as load_yaml
from bs4 import BeautifulSoup
#import json
import base64
import yaml
import re
import os



from crispy_forms.layout import Layout, Field

def correct_error_id(soup):
    if len(soup.find_all(id="reference"))>1:
        ele1=soup.findAll(id="reference")[0]
        ele1['id']='crossreference'
    if len(soup.find_all(id="litermethod"))>1:
        ele2=soup.findAll(id="litermethod")[0]
        ele2['id']='crosstime'      

def id_list(soup):
    result=[]
    for tag in soup.find_all(True, {'id':True}):
        result.append(tag['id'])
    return result
    
def idname(soup, the_id):
    items = soup.find_all(id=the_id)
    if len(items) == 1:
        item , = items
        content = item.contents[0]
        if u'\u2003\u2003' in content:
            li_ascii = content.split(u'\u2003\u2003')
            return li_ascii[0]
        else:
            return content
    elif len(items) > 1:
        result=[]
        for item in items:
            content = item.contents[0]
            if u'\u2003\u2003' in content:
                li_ascii = content.split(u'\u2003\u2003')
                result.append(li_ascii[0])
            else:
                result.append(content)
        return result            
        

def idvalue(soup, the_id):
    item = soup.find(id=the_id)
    if item:
        if len(item.contents) > 0:
            content = item.contents[0]
            if u'\u2003\u2003' in content:
                li_ascii = content.split(u'\u2003\u2003')
                #li = json.dumps(li_ascii, ensure_ascii=False)
                
                # when the value is not a number
                if the_id in ['encrypt1', 'encrypt2', 'email', 'litermethod']:
                    return li_ascii[1]
                elif the_id == 'noevenodd':
                    if li_ascii[1] == u"无":
                        return 0
                    else:
                        string_noevenodd = li_ascii[1]
                        list_noevenodd = string_noevenodd.split(" ")
#                        print list_noevenodd
                        n_noevenodd = len(list_noevenodd)
                        return n_noevenodd
                elif li_ascii[1]=="True":
                    return True
                elif li_ascii[1]=="False":
                    return False
                elif re.match("^[-0-9]+$",li_ascii[1]):
                    return int(li_ascii[1])
                elif re.match("^[-0-9.]+$",li_ascii[1]):
                    return float(li_ascii[1])
                else:
                    return None
            else:
                if the_id == 'email':
                    return content
                elif the_id == 'checkfile_version':
                    return content
                else:
                    return None
        else:
            return None
    else:
        return None



def fullIDName(soup, id):
    fullname=""
    
    
    return fullname

def answer_to_html(answer_data):
    fake = base64.b64decode(answer_data)
    return true_string(fake)
    
    
def true_string(fake):
    truestring = fake[1:-1]
#    try:
#        base64.b64decode(truestring)
#    except TypeError:
#        #print true_string
#        return None
    
    return base64.b64decode(truestring)


#}}}


# {{{ my upload question

class myFileUploadForm(FileUploadForm):
    uploaded_file = forms.FileField(required=True, label=_('Uploaded file'))

    def clean_uploaded_file(self):
        uploaded_file = self.cleaned_data['uploaded_file']
        from django.template.defaultfilters import filesizeformat

        if uploaded_file._size > self.max_file_size:
            raise forms.ValidationError(
                    _("Please keep file size under %(allowedsize)s. "
                    "Current filesize is %(uploadedsize)s.")
                    % {'allowedsize': filesizeformat(self.max_file_size),
                        'uploadedsize': filesizeformat(uploaded_file._size)})

        if self.mime_types is not None and self.mime_types == ["application/pdf"]:
            if uploaded_file.read()[:4] != "%PDF":
                raise forms.ValidationError(_("Uploaded file is not a PDF."))
        elif self.mime_types is not None and self.mime_types == ["application/vnd.ms-word.document.macroEnabled.12"]:
            if uploaded_file:
                thefilename=uploaded_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
#                print thefilename
#                print ext
                if ext !=".docm":
                    raise forms.ValidationError("文件类型不支持！请上传后缀为docm的文档！")
        elif self.mime_types is not None and self.mime_types == ["application/vnd.ms-excel.sheet.macroEnabled.12"]:
            if uploaded_file:
                thefilename=uploaded_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
#                print thefilename
#                print ext
                if ext !=".xlsm":
                    raise forms.ValidationError("文件类型不支持！请上传后缀为xlsm的文档！")    
        elif self.mime_types is not None and self.mime_types == ["text/plain"]:
            if uploaded_file:
                thefilename=uploaded_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
#                print thefilename
#                print ext
                if ext !=".txt":
                    raise forms.ValidationError("请上传txt文件！")
        elif self.mime_types is not None and self.mime_types == ["application/zip"]:
            if uploaded_file:
                thefilename=uploaded_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
#                print thefilename
#                print ext
                if ext !=".zip":
                    raise forms.ValidationError("请压缩成zip文件后上传！")
        return uploaded_file

class FileUploadQuestion(FUQ):
    # Override default FileUploadQuestion to allow more mime-type

    ALLOWED_MIME_TYPES = [
            "application/pdf",
            "text/plain",
            "application/msword",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "application/vnd.ms-excel.sheet.macroEnabled.12",
            "application/vnd.ms-word.document.macroEnabled.12",
            "application/octet-stream",
            "application/zip",
            ]

    def __init__(self, vctx, location, page_desc):
        super(FileUploadQuestion, self).__init__(vctx, location, page_desc)

        if not (set(page_desc.mime_types) <= set(self.ALLOWED_MIME_TYPES)):
            raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("unrecognized mime types"),
                        " '%(presenttype)s'")
                    % {
                        'location': location,
                        'presenttype': ", ".join(
                            set(page_desc.mime_types)
                            - set(self.ALLOWED_MIME_TYPES))})

        if vctx is not None:
            if not hasattr(page_desc, "value"):
                vctx.add_warning(location, _("upload question does not have "
                        "assigned point value"))

    def make_form(self, page_context, page_data,
            answer_data, page_behavior):
        form = myFileUploadForm(
                self.page_desc.maximum_megabytes, self.page_desc.mime_types)
        return form

    def process_form_post(self, page_context, page_data, post_data, files_data,
            page_behavior):
        form = myFileUploadForm(
                self.page_desc.maximum_megabytes, self.page_desc.mime_types,
                post_data, files_data)
        return form

    def normalized_bytes_answer(self, page_context, page_data, answer_data):
        if answer_data is None:
            return None

        ext = None
        if len(self.page_desc.mime_types) == 1:
            mtype, = self.page_desc.mime_types
            if mtype == "application/vnd.ms-word.document.macroEnabled.12":
                ext = ".docm"
            elif mtype == "application/vnd.ms-excel.sheet.macroEnabled.12":
                ext = ".xlsm"
            else:
                from mimetypes import guess_extension
                ext = guess_extension(mtype)

        if ext is None:
            ext = ".dat"

        from base64 import b64decode
        return (ext, b64decode(answer_data["base64_data"]))

class myFileUploadQuestion(FUQ):

    ALLOWED_MIME_TYPES = [
            "application/pdf",
            "text/plain",
            "application/msword",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "application/vnd.ms-word.document.macroEnabled.12",
            "application/vnd.ms-excel.sheet.macroEnabled.12",
            "application/octet-stream",
            "application/zip",
            ]

    def __init__(self, vctx, location, page_desc):
        super(myFileUploadQuestion, self).__init__(vctx, location, page_desc)
        
        from django.core.exceptions import ObjectDoesNotExist

        if vctx is not None and hasattr(page_desc, "criteria"):
                try:
                    if not isinstance(page_desc.criteria, str):
                        raise ObjectDoesNotExist()

                    from course.content import get_repo_blob
                    get_repo_blob(vctx.repo, page_desc.criteria, vctx.commit_sha)
                except ObjectDoesNotExist:
                    raise ValidationError("%s: criteria file '%s' not found"
                            % (location, page_desc.criteria))
                    
        if vctx is not None and hasattr(page_desc, "demo_html"):
                try:
                    if not isinstance(page_desc.demo_html, str):
                        raise ObjectDoesNotExist()

                    from course.content import get_repo_blob
                    get_repo_blob(vctx.repo, page_desc.demo_html, vctx.commit_sha)
                except ObjectDoesNotExist:
                    raise ValidationError("%s: demo_html file '%s' not found"
                            % (location, page_desc.demo_html))                    

    def allowed_attrs(self):
        return super(myFileUploadQuestion, self).allowed_attrs() + (
            ("criteria", str), 
            ("demo_html", str),
            ("my_word_file", bool),
        )

    display_criteria=""
    display_feedback=""

    def append_display(self, attrib, append_str):
        if attrib.display == True:
            self.display_criteria += "<li><b>" + attrib.fullname + "</b>  " + append_str + "</li>"
            
    def append_feedback(self, attrib, append_str):
        if attrib.display == True:
            self.display_feedback += "<li><b>" + attrib.fullname + "</b>  " + append_str + "</li>"            

    def body_append(self, page_context):
        
        demo_html = get_repo_blob(
            page_context.repo,
            self.page_desc.demo_html,
            page_context.commit_sha).data
        
        soup = BeautifulSoup(demo_html)
        correct_error_id(soup)
        all_id=id_list(soup)
        
        all_criteria = dict_to_struct(
            load_yaml(
                get_repo_blob(
                    page_context.repo,
                    self.page_desc.criteria,
                    page_context.commit_sha).data
            )
        )

        for the_id in all_id:
            if hasattr(all_criteria, the_id):         
                attrib = getattr(all_criteria, the_id)
                attrib_fullname = attrib.fullname
                attrib_display = attrib.display
                attrib_use = attrib.use
                if attrib_use:
                    if hasattr(attrib, "type"):
                        if attrib.type == "boolean":
                                self.append_display(attrib, u"标准:" + unicode(attrib.standard) + u", 否则扣分:" + unicode(-attrib.credit))
                        elif attrib.type == "max":
                                if attrib.per == True:
                                    self.append_display(attrib, u"标准:不超过" + unicode(attrib.standard) + u"处, 每超1处扣分:" + unicode(-attrib.credit))
                                else:
                                    self.append_display(attrib, u"标准:不超过" + unicode(attrib.standard) + u"处, 超出扣分:" + unicode(-attrib.credit))
                        elif attrib.type == "min":
                                if attrib.per == True:
                                    self.append_display(attrib, u"标准:不少于" + unicode(attrib.standard) + u"处, 每少1处扣分:" + unicode(-attrib.credit))
                                else:
                                    self.append_display(attrib, u"标准:不少于" + unicode(attrib.standard) + u"处, 不足扣分:" + unicode(-attrib.credit))
                        elif attrib.type == "text":
                                if attrib.per == True:
                                    self.append_display(attrib, u"标准:" + unicode(attrib.standard) + u", 每错1处扣分:" + unicode(-attrib.credit))
                                else:
                                    self.append_display(attrib, u"标准:" + unicode(attrib.standard) + u", 否则扣分:" + unicode(-attrib.credit))
        
        return self.display_criteria
                                
                                
                                
    def body(self, page_context, page_data):
        body_temp = markup_to_html(page_context, self.page_desc.prompt)
        
        show_word_file = getattr(self.page_desc, "my_word_file", False)
        
        if show_word_file:
#            print page_context.flow_session.participation.user.institutional_id
#            print page_context.flow_session.participation.user.institutional_id + ".docm"

            if page_context.flow_session.participation.user.institutional_id:
                filename = page_context.flow_session.participation.user.institutional_id + ".docm"
                file_html= u"<div><p class='h3'>待排版文档下载 <a href='media:docs/my_word_file/%s'><i class='fa fa-download'></i>下载</a></p><p class='warning'>如果没有待排版文档（404错误），请尽快与老师联系.</p></div>" % filename
            else:
                file_html= u"<div><p class='h3'>待排版文档下载 <a href='media:docs/my_word_file/none.docm'><i class='fa fa-download'></i>下载</a></p><p class='warning'>如果没有待排版文档（404错误），请尽快与老师联系.</p></div>"

            
#            print file_html
            
            body_temp += markup_to_html(page_context, file_html)

        if hasattr(self.page_desc, "criteria") and hasattr(self.page_desc, "demo_html"):
            body_temp += u"<div class='accordion'> <h3>评分标准</h3><div><ol>" + self.body_append(page_context) + "</ol></div> </div> <br/>"

        return body_temp


    def files_data_to_answer_data(self, files_data):
        files_data["uploaded_file"].seek(0)
        buf = files_data["uploaded_file"].read()
        
        md5string = None
        sha1string = None
        
        import hashlib
        md5string = hashlib.md5(buf).hexdigest()        
        sha1string = hashlib.sha1(buf).hexdigest()
                

        if len(self.page_desc.mime_types) == 1:
            mime_type, = self.page_desc.mime_types
        else:
            mime_type = files_data["uploaded_file"].content_type
        from base64 import b64encode
        return {
            "base64_data": b64encode(buf),
            "mime_type": mime_type,
            "md5": md5string,
            "sha1": sha1string 
                }
    def make_form(self, page_context, page_data,
            answer_data, page_behavior):
        form = myFileUploadForm(
                self.page_desc.maximum_megabytes, self.page_desc.mime_types)
        return form

    def process_form_post(self, page_context, page_data, post_data, files_data,
            page_behavior):
        form = myFileUploadForm(
                self.page_desc.maximum_megabytes, self.page_desc.mime_types,
                post_data, files_data)
        return form

    def form_to_html(self, request, page_context, form, answer_data):
        ctx = {"form": form}
        if answer_data is not None:
            ctx["mime_type"] = answer_data["mime_type"]
            ctx["data_url"] = "data:%s;base64,%s" % (
                answer_data["mime_type"],
                answer_data["base64_data"],
                )

        from django.template import RequestContext
        from django.template.loader import render_to_string
        return render_to_string(
                "course/file-upload-form.html",
                RequestContext(request, ctx))

    def answer_data(self, page_context, page_data, form, files_data):
        return self.files_data_to_answer_data(files_data)

    def get_edit_grade(self, page_context, page_data, answer_data):
        edit_grade={}
        if answer_to_html(answer_data["base64_data"]) is None:
            edit_grade["correctness"] = 0
            edit_grade["bulk_feedback"] = "<p>上传的文件不是自动生成的效验码，不得分</p>"
            return dict_to_struct(edit_grade)
        
        soup = BeautifulSoup(answer_to_html(answer_data["base64_data"]))
        correct_error_id(soup)
        total_percent = 100
        add_minus = 0
        all_criteria = dict_to_struct(
            load_yaml(
                get_repo_blob(
                    page_context.repo,
                    self.page_desc.criteria,
                    page_context.commit_sha).data
            )
        )

        all_id=id_list(soup)

        for the_id in all_id:
            if hasattr(all_criteria, the_id):         
                #print idname(soup, id), idvalue(soup,id)
                real_value = idvalue(soup, the_id)
                attrib = getattr(all_criteria, the_id)
                attrib_fullname = attrib.fullname
                attrib_display = attrib.display
                attrib_use = attrib.use
                #attrib_type = attrib.type
                #attrib_standard = attrib.standard
                #attrib_per = attrib.per
                #attrib_credit = attrib.credit
                #print attrib_fullname
                if the_id == "email":
#                    print attrib_fullname, real_value
#                    print page_context.flow_session.participation.user.institutional_id
                    if real_value <> page_context.flow_session.participation.user.institutional_id:
                        if not is_course_staff(page_context):
                            edit_grade["correctness"] = 0
                            edit_grade["bulk_feedback"] = u"学号不符"
                            #print page_context.flow_session.participation.user.last_name + page_context.flow_session.participation.user.first_name, u"学号不符"
                            return dict_to_struct(edit_grade)
                            #raise RuntimeError(u"学号不符")
                if the_id == "checkfile_version":
                    print attrib.standard
                    print attrib_fullname, real_value
                if hasattr(attrib, "type"):
                    if attrib.type == "boolean":
                        if real_value <> attrib.standard:
                            add_minus += abs(attrib.credit)
                            self.append_feedback(attrib, u"标准:" 
                                                + unicode(attrib.standard) 
                                                + u", 扣分:"
                                                + unicode(abs(attrib.credit))
                                                + u"; <tag style='color:red'>结果: 扣"
                                                + unicode(abs(attrib.credit))
                                                + u"分</tag>.")
                            #print 
                            #print attrib_fullname, attrib.credit
                    elif attrib.type == "max":
                        if real_value > attrib.standard:
                            if attrib.per == True:
                                add_minus += abs((real_value - attrib.standard) * attrib.credit)
                                self.append_feedback(attrib,
                                                    u"标准:不超过"
                                                    + unicode(attrib.standard)
                                                    + u"处, 每超1处扣分:"
                                                    + unicode(abs(attrib.credit))
                                                    + u"; <tag style='color:red'>结果: 扣"
                                                    + unicode(abs((real_value - attrib.standard) * attrib.credit))
                                                    + u"分</tag>.")
                            else:
                                add_minus += attrib.credit
                                self.append_feedback(attrib,
                                                    u"标准:不超过"
                                                    + unicode(attrib.standard)
                                                    + u"处, 超出扣分:"
                                                    + unicode(abs(attrib.credit))
                                                    + u"; <tag style='color:red'>结果: 扣"
                                                    + unicode(abs(attrib.credit))
                                                    + u"分</tag>.")
                    elif attrib.type == "min":
                        if real_value < attrib.standard:
                            if attrib.per == True:
                                add_minus += abs((real_value - attrib.standard) * attrib.credit)
                                self.append_feedback(attrib,
                                                    u"标准:不少于"
                                                    + unicode(attrib.standard)
                                                    + u"处, 每少1处扣分:"
                                                    + unicode(abs(attrib.credit))
                                                    + u"; <tag style='color:red'>结果: 扣"
                                                    + unicode(abs((real_value - attrib.standard) * attrib.credit))
                                                    + u"分</tag>.")
                            else:
                                add_minus += abs(attrib.credit)
                                self.append_feedback(attrib,
                                                    u"标准:不少于"
                                                    + unicode(attrib.standard)
                                                    + u"处, 不足扣分:"
                                                    + unicode(abs(attrib.credit))
                                                    + u"; <tag style='color:red'>结果: 扣"
                                                    + unicode(abs(attrib.credit))
                                                    + u"分</tag>.")
        correctness = (total_percent - add_minus)/100
        if correctness < 0:
            correctness = 0
        
                
        edit_grade["correctness"] = correctness
        edit_grade["bulk_feedback"] = self.display_feedback
        
        return dict_to_struct(edit_grade)
    
    def get_nonhuman_feedback(self, page_context, page_data, answer_data):
        correctness = None
        feedback = None
        
        if answer_data["mime_type2"] == "text/plain":
            nonhuman_feedback = self.get_edit_grade(page_context, page_data, answer_data)
            correctness = nonhuman_feedback.correctness
            bulk_feedback = nonhuman_feedback.bulk_feedback
            auto_feedback = u"<p>你得到了总分数的 %.1f %% .</p>" % (correctness*100)
            if correctness == 1:
                feedback = auto_feedback
            else:
                feedback = "".join([auto_feedback, u"<p>以下是扣分的情况(百分制)：</p>", bulk_feedback])

        if answer_data["mime_type"] == "application/vnd.ms-word.document.macroEnabled.12":
            
            import os
            user_full_name = page_context.flow_session.participation.user.last_name + page_context.flow_session.participation.user.first_name
            user_email = page_context.flow_session.participation.user.institutional_id
            user_name = page_context.flow_session.participation.user.username
            if user_full_name is not None:
                the_name = user_full_name
            elif user_email is not None:
                the_name = user_email
            else:
                the_name = user_name
            
            course_name = page_context.course.identifier
            flow_id = page_context.flow_session.flow_id
            
            file_save_path = str(os.path.join(settings.BASE_DIR, "savefile", course_name, flow_id, the_name))
            file_name = str(page_context.flow_session.id) + ".docm"
            
            if not os.path.isdir(file_save_path.decode('utf-8')):
                os.makedirs(file_save_path.decode('utf-8'))
                
            if not os.path.exists(os.path.join(file_save_path.decode('utf-8'), file_name)):
                fh = open(os.path.join(file_save_path, file_name).decode('utf-8'), "wb")
                filedata = answer_data["base64_data"]
                fh.write(filedata.decode('base64'))
        
        return AnswerFeedback(
            correctness=correctness,
            feedback=feedback
            )

    def grade(self, page_context, page_data, answer_data, grade_data):
        if answer_data is None:
            return AnswerFeedback(correctness=0,
                    feedback=_("No answer provided."))

        if grade_data is not None and not grade_data["released"]:
            grade_data = None
            
        code_feedback = self.get_nonhuman_feedback(page_context, page_data, answer_data)
        
        
        total_points = self.page_desc.value
        human_points = total_points
        code_points = total_points

        correctness = None
        percentage = None
        if (code_feedback is not None
            and code_feedback.correctness is not None
            and grade_data is not None
            and grade_data["grade_percent"] is not None):
            correctness = (
                code_feedback.correctness * total_points
                + grade_data["grade_percent"] / 100
                * total_points
            ) / total_points
            percentage = correctness * 100
        elif (code_feedback is not None
              and code_feedback.correctness is not None
              and grade_data is None):
            correctness = code_feedback.correctness
            percentage = correctness * 100

        human_feedback_percentage = None
        human_feedback_text = None

        human_feedback_points = None
        if grade_data is not None:
            if grade_data["feedback_text"] is not None:
                human_feedback_text = markup_to_html(
                        page_context, grade_data["feedback_text"])

            human_feedback_percentage = grade_data["grade_percent"]
            if human_feedback_percentage is not None:
                human_feedback_points = (human_feedback_percentage/100.
                        * human_points)

        code_feedback_points = None
        if (code_feedback is not None
                and code_feedback.correctness is not None):
            code_feedback_points = code_feedback.correctness*code_points
            
        if (code_feedback_points is not None
            and human_feedback_points is not None):
            if code_feedback_points + human_feedback_points > total_points + 0.1:
#                print code_feedback_points, human_feedback_points, total_points
                raise RuntimeError(u"flow%s的总评成绩超过了100%%，人工成绩不得超过%s分或%s%%" %
                                   (page_context.flow_session.id,
                                    total_points - code_feedback_points,
                                    (total_points - code_feedback_points)*100/total_points)
                                  )

        from django.template.loader import render_to_string
#        if percentage < 0.6:
#            print page_context.flow_session.participation.user.last_name + page_context.flow_session.participation.user.first_name, page_context.flow_session.id, u"分数过低", percentage
        feedback = render_to_string(
                "course/feedback-code-with-human.html",
                {
                    "percentage": percentage,
                    "code_feedback": code_feedback,
                    "code_feedback_points": code_feedback_points,
                    "code_points": code_points,
                    "human_feedback_text": human_feedback_text,
                    "human_feedback_points": human_feedback_points,
                    "human_points": human_points,
                    })

        return AnswerFeedback(
                correctness=correctness,
                feedback=feedback,
                #bulk_feedback=code_feedback.bulk_feedback
        )
# }}}

        


class WordUploadQuestion(myFileUploadQuestion):

    ALLOWED_MIME_TYPES = [
            "application/pdf",
            "text/plain",
            "application/msword",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "application/vnd.ms-word.document.macroEnabled.12",
            "application/octet-stream",
            "application/zip",
            ]

    def __init__(self, vctx, location, page_desc):
        super(WordUploadQuestion, self).__init__(vctx, location, page_desc)

        for mime_type in page_desc.mime_types:
            if mime_type not in self.ALLOWED_MIME_TYPES:                
                raise ValidationError(
                        string_concat(
                            "%(location)s: ",
                            _("unrecognized mime types"),
                            " '%(presenttype)s'")
                    % {
                        'location': location,
                        'presenttype': mime_type,
                        
                    })
            else:
                if len(self.page_desc.mime_types) == 1:
                    self.mime_type, = self.page_desc.mime_types
                    self.mime_type1, = self.page_desc.mime_types
                    self.mime_type2, = self.page_desc.mime_types
                else:
                    self.mime_type = self.page_desc.mime_types[0]
                    self.mime_type1 = self.page_desc.mime_types[0]
                    self.mime_type2 = self.page_desc.mime_types[1]

        if hasattr(self.page_desc, "maximum_megabytes2"):
            self.maximum_megabytes = self.page_desc.maximum_megabytes
            self.maximum_megabytes1 = self.page_desc.maximum_megabytes
            self.maximum_megabytes2 = self.page_desc.maximum_megabytes2
        else:
            self.maximum_megabytes = self.page_desc.maximum_megabytes
            self.maximum_megabytes1 = self.page_desc.maximum_megabytes
            self.maximum_megabytes2 = self.page_desc.maximum_megabytes
            
        if not hasattr(page_desc, "value"):
            vctx.add_warning(location, _("upload question does not have "
                    "assigned point value"))
            
        from django.core.exceptions import ObjectDoesNotExist

        if vctx is not None and hasattr(page_desc, "criteria"):
                try:
                    if not isinstance(page_desc.criteria, str):
                        raise ObjectDoesNotExist()

                    from course.content import get_repo_blob
                    get_repo_blob(vctx.repo, page_desc.criteria, vctx.commit_sha)
                except ObjectDoesNotExist:
                    raise ValidationError("%s: criteria file '%s' not found"
                            % (location, page_desc.criteria))
                    
        if vctx is not None and hasattr(page_desc, "demo_html"):
                try:
                    if not isinstance(page_desc.demo_html, str):
                        raise ObjectDoesNotExist()

                    from course.content import get_repo_blob
                    get_repo_blob(vctx.repo, page_desc.demo_html, vctx.commit_sha)
                except ObjectDoesNotExist:
                    raise ValidationError("%s: demo_html file '%s' not found"
                            % (location, page_desc.demo_html))

#    def required_attrs(self):
#        return super(WordUploadQuestion, self).required_attrs() + (
#                ("prompt", "markup"),
#                ("mime_types", list),
#                ("maximum_megabytes", (int, float)),
#                )

    def allowed_attrs(self):
        return super(WordUploadQuestion, self).allowed_attrs() + (
            ("criteria", str), 
            ("demo_html", str),
            ("maximum_megabytes2", (int, float)),
            ("same_as_sample", bool),
                )

#    display_criteria=""
#    display_feedback=""


    def files_data_to_answer_data(self, files_data):
        files_data["word_file"].seek(0)
        buf = files_data["word_file"].read()
        files_data["check_file"].seek(0)
        buf2 = files_data["check_file"].read()
        
        md5string = None
        sha1string = None
        
        import hashlib
        md5string = hashlib.md5(buf).hexdigest()
        md5string2 = hashlib.md5(buf).hexdigest()  
        sha1string = hashlib.sha1(buf).hexdigest()
        sha1string2 = hashlib.sha1(buf).hexdigest()
        
        from base64 import b64encode
        return {
            "base64_data": b64encode(buf),
            "mime_type": self.mime_type1,
            "md5": md5string,
            "sha1": sha1string,
            "base64_data2": b64encode(buf2),
            "mime_type2": self.mime_type2,
            "md52": md5string2,
            "sha12": sha1string2,
                }

    def make_form(self, page_context, page_data,
            answer_data, page_behavior):
        form = WordUploadForm(
            self.maximum_megabytes1, 
            self.mime_type1, 
            self.maximum_megabytes2, 
            self.mime_type2,
            page_context.flow_session.participation.user.institutional_id,
            self._get_version(page_context),
            page_context.flow_session.participation.user.is_superuser
        )
        return form

    def process_form_post(self, page_context, page_data, post_data, files_data,
            page_behavior):
        form = WordUploadForm(
            self.maximum_megabytes1, 
            self.mime_type1, 
            self.maximum_megabytes2, 
            self.mime_type2,
            page_context.flow_session.participation.user.institutional_id,
            self._get_version(page_context),
            page_context.flow_session.participation.user.is_superuser,
            post_data,
            files_data
        )
        return form
    
    def is_course_staff(self, page_context):
        if page_context.flow_session.participation.role in [participation_role.instructor, participation_role.teaching_assistant]:
            return True
        else:
            return False

    def form_to_html(self, request, page_context, form, answer_data):
        ctx = {"form": form}
        if answer_data is not None:
            ctx["mime_type"] = answer_data["mime_type"]
            ctx["data_url"] = "data:%s;base64,%s" % (
                answer_data["mime_type"],
                answer_data["base64_data"],
                )

        from django.template import RequestContext
        from django.template.loader import render_to_string
        return render_to_string(
                "course/file-upload-form.html",
                RequestContext(request, ctx))

    def _get_all_criteria(self, page_context):
        all_criteria = dict_to_struct(
            load_yaml(
                get_repo_blob(
                    page_context.repo,
                    self.page_desc.criteria,
                    page_context.commit_sha).data
            )
        )

        return all_criteria


    def _get_version(self, page_context):
        all_criteria = self._get_all_criteria(page_context)
        version_offset = all_criteria.checkfile_version.standard
        return version_offset

    def get_edit_grade(self, page_context, page_data, answer_data):
        edit_grade={}
        if answer_to_html(answer_data["base64_data2"]) is None:
            edit_grade["correctness"] = 0
            edit_grade["bulk_feedback"] = "<p>上传的文件不是自动生成的校验码</p>"
            return dict_to_struct(edit_grade)

        same_as_sample = getattr(self.page_desc, "same_as_sample", False)
        if same_as_sample:
            demo_html = get_repo_blob(
                page_context.repo,
                self.page_desc.demo_html,
                page_context.commit_sha).data
            soup_id = BeautifulSoup(demo_html)
            correct_error_id(soup_id)
            all_id=id_list(soup_id)
        else:
            soup = BeautifulSoup(answer_to_html(answer_data["base64_data2"]))
            all_id=id_list(soup)

        soup = BeautifulSoup(answer_to_html(answer_data["base64_data2"]))
        correct_error_id(soup)
        total_percent = 100
        add_minus = 0
        all_criteria = self._get_all_criteria(page_context)

        for the_id in all_id:
#            print the_id
            if hasattr(all_criteria, the_id):
                real_value = idvalue(soup, the_id)
                attrib = getattr(all_criteria, the_id)
                attrib_fullname = attrib.fullname
                attrib_display = attrib.display
                attrib_use = attrib.use
                if same_as_sample:
                    attrib_use = attrib_use or same_as_sample

                if attrib_use:
#                    if the_id == "checkfile_version":
#                        print attrib.standard
#                        print attrib_fullname, real_value
                    if hasattr(attrib, "type"):
                        if attrib.type == "boolean":
                            if real_value <> attrib.standard:
                                add_minus += attrib.credit
                                self.append_feedback(attrib, u"标准:" 
                                                    + unicode(attrib.standard) 
                                                    + u", 扣分:"
                                                    + unicode(-attrib.credit) 
                                                    + u"; <tag style='color:red'>结果: 扣"
                                                    + unicode(-attrib.credit)
                                                    + u"分</tag>.")

                        elif attrib.type == "max":
                            if real_value > attrib.standard:
                                if attrib.per == True:
                                    add_minus += (real_value - attrib.standard) * attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:不超过"
                                                        + unicode(attrib.standard)
                                                        + u"处, 每超1处扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs((real_value - attrib.standard) * attrib.credit))
                                                        + u"分</tag>.")
                                else:
                                    add_minus += attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:不超过"
                                                        + unicode(attrib.standard)
                                                        + u"处, 超出扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs(attrib.credit))
                                                        + u"分</tag>.")
                        elif attrib.type == "min":
                            if real_value < attrib.standard:
                                if attrib.per == True:
                                    add_minus += -(real_value - attrib.standard) * attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:不少于"
                                                        + unicode(attrib.standard)
                                                        + u"处, 每少1处扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs((real_value - attrib.standard) * attrib.credit))
                                                        + u"分</tag>.")
                                else:
                                    add_minus += attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:不少于"
                                                        + unicode(attrib.standard)
                                                        + u"处, 不足扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs(attrib.credit))
                                                        + u"分</tag>.")
                        elif attrib.type == "text":
                            print "real_value", real_value
                            print "attrib.standard", attrib.standard
                            if real_value != attrib.standard:
                                if attrib.per == True:
                                    add_minus += -(real_value - attrib.standard) * attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:"
                                                        + unicode(attrib.standard)
                                                        + u", 每错1处扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs((real_value - attrib.standard) * attrib.credit))
                                                        + u"分</tag>.")
                                else:
                                    add_minus += attrib.credit
                                    self.append_feedback(attrib,
                                                        u"标准:"
                                                        + unicode(attrib.standard)
                                                        + u", 否则扣分:"
                                                        + unicode(abs(attrib.credit))
                                                        + u"; <tag style='color:red'>结果: 扣"
                                                        + unicode(abs(attrib.credit))
                                                        + u"分</tag>.")
        correctness = (total_percent + add_minus)/100
        if correctness < 0:
            correctness = 0  

        edit_grade["correctness"] = correctness
        edit_grade["bulk_feedback"] = self.display_feedback
        
        return dict_to_struct(edit_grade)
    
    def get_nonhuman_feedback(self, page_context, page_data, answer_data):
        correctness = None
        feedback = None
        
        if answer_data["mime_type2"] == "text/plain":
            nonhuman_feedback = self.get_edit_grade(page_context, page_data, answer_data)
            correctness = nonhuman_feedback.correctness
            bulk_feedback = nonhuman_feedback.bulk_feedback
            auto_feedback = u"<p>你得到了总分数的 %.1f %% .</p>" % (correctness*100)
            if correctness == 1:
                feedback = auto_feedback
            else:
                feedback = "".join([auto_feedback, u"<p>以下是扣分的情况(百分制)：</p>", bulk_feedback])

        if answer_data["mime_type"] == "application/vnd.ms-word.document.macroEnabled.12":
            
            import os
            user_full_name = page_context.flow_session.participation.user.last_name + page_context.flow_session.participation.user.first_name
            user_email = page_context.flow_session.participation.user.institutional_id
            user_name = page_context.flow_session.participation.user.username
            if user_full_name is not None:
                the_name = user_full_name
            elif user_email is not None:
                the_name = user_email
            else:
                the_name = user_name

            course_name = page_context.course.identifier
            flow_id = page_context.flow_session.flow_id
            
            file_save_path = str(os.path.join(settings.BASE_DIR, "savefile", course_name, flow_id, the_name))
            file_name = str(page_context.flow_session.id) + ".docm"
            
            if not os.path.isdir(file_save_path.decode('utf-8')):
                os.makedirs(file_save_path.decode('utf-8'))
                
            if not os.path.exists(os.path.join(file_save_path.decode('utf-8'), file_name)):
                fh = open(os.path.join(file_save_path, file_name).decode('utf-8'), "wb")
                filedata = answer_data["base64_data"]
                fh.write(filedata.decode('base64'))
        
        return AnswerFeedback(
            correctness=correctness,
            feedback=feedback
            )

    def grade(self, page_context, page_data, answer_data, grade_data):
        if answer_data is None:
            return AnswerFeedback(correctness=0,
                    feedback=_("No answer provided."))

        if grade_data is not None and not grade_data["released"]:
            grade_data = None
            
        code_feedback = self.get_nonhuman_feedback(page_context, page_data, answer_data)
        
        
        total_points = self.page_desc.value
        human_points = total_points
        code_points = total_points

        correctness = None
        percentage = None
        if (code_feedback is not None
            and code_feedback.correctness is not None
            and grade_data is not None
            and grade_data["grade_percent"] is not None):
            correctness = (
                code_feedback.correctness * total_points
                + grade_data["grade_percent"] / 100
                * total_points
            ) / total_points
            percentage = correctness * 100
        elif (code_feedback is not None
              and code_feedback.correctness is not None
              and grade_data is None):
            correctness = code_feedback.correctness
            percentage = correctness * 100

        human_feedback_percentage = None
        human_feedback_text = None

        human_feedback_points = None
        if grade_data is not None:
            if grade_data["feedback_text"] is not None:
                human_feedback_text = markup_to_html(
                        page_context, grade_data["feedback_text"])

            human_feedback_percentage = grade_data["grade_percent"]
            if human_feedback_percentage is not None:
                human_feedback_points = (human_feedback_percentage/100.
                        * human_points)

        code_feedback_points = None
        if (code_feedback is not None
                and code_feedback.correctness is not None):
            code_feedback_points = code_feedback.correctness*code_points
            
            
#        if (code_feedback_points is not None
#            and human_feedback_points is not None):
#            if code_feedback_points + human_feedback_points > total_points + 0.1:                
#                raise RuntimeError(u"flow%s的总评成绩超过了100%%，人工成绩不得超过%s分或%s%%" %
#                                   (page_context.flow_session.id,
#                                    total_points - code_feedback_points,
#                                    (total_points - code_feedback_points)*100/total_points)
#                                  )

        from django.template.loader import render_to_string
        feedback = render_to_string(
                "course/feedback-code-with-human.html",
                {
                    "percentage": percentage,
                    "code_feedback": code_feedback,
                    "code_feedback_points": code_feedback_points,
                    "code_points": code_points,
                    "human_feedback_text": human_feedback_text,
                    "human_feedback_points": human_feedback_points,
                    "human_points": human_points,
                    })

        return AnswerFeedback(
                correctness=correctness,
                feedback=feedback,
        )
    def normalized_bytes_answer(self, page_context, page_data, answer_data):
        if answer_data is None:
            return None

        ext = None
        if len(self.page_desc.mime_types) == 1:
            mtype, = self.page_desc.mime_types
            if mtype == "application/vnd.ms-word.document.macroEnabled.12":
                ext = ".docm"
            elif mtype == "application/vnd.ms-excel.sheet.macroEnabled.12":
                ext = ".xlsm"
            else:
                from mimetypes import guess_extension
                ext = guess_extension(mtype)
        elif "application/vnd.ms-word.document.macroEnabled.12" in self.page_desc.mime_types:
                ext = ".docm"
        elif "application/vnd.ms-excel.sheet.macroEnabled.12" in self.page_desc.mime_types:
                ext = ".xlsm"

        if ext is None:
            ext = ".dat"

        from base64 import b64decode
        return (ext, b64decode(answer_data["base64_data"]))

# }}}

class WordUploadForm(StyledForm):
    show_save_button = False
    word_file = forms.FileField(required=True, label='.docm排版文档')
    check_file = forms.FileField(required=True, label='txt校验文档')

    def __init__(self, maximum_megabytes1, mime_types1, maximum_megabytes2, mime_types2, institutional_id, base_version, is_course_staff, *args, **kwargs):
        super(WordUploadForm, self).__init__(*args, **kwargs)

        self.word_max_file_size = maximum_megabytes1 * 1024**2
        self.word_mime_types = mime_types1
        self.check_max_file_size = maximum_megabytes2 * 1024**2
        self.check_mime_types = mime_types2
        self.institutional_id = institutional_id
        self.base_version = base_version
        self.is_course_staff = is_course_staff
        
        self.helper.layout = Layout(
            Field("word_file", accept=mime_types1),
            Field("check_file", accept=mime_types2),
        )

    def upload_word_file(self):
        word_file = self.cleaned_data.get('word_file')
        if word_file is None:
            self.add_error('word_file', _("This field is required."))
        else:
            from django.template.defaultfilters import filesizeformat

            if word_file._size > self.word_max_file_size:
                raise forms.ValidationError(
                        _("Please keep file size under %(allowedsize)s. "
                        "Current filesize is %(uploadedsize)s.")
                        % {'allowedsize': filesizeformat(self.word_max_file_size),
                            'uploadedsize': filesizeformat(word_file._size)})

            if self.word_mime_types is not None and self.word_mime_types == "application/pdf":
                if word_file.read()[:4] != "%PDF":
                    raise forms.ValidationError(_("Uploaded file is not a PDF."))
            elif self.word_mime_types is not None and self.word_mime_types == "application/vnd.ms-word.document.macroEnabled.12":
                if word_file:
                    thefilename=word_file.name                
                    ext = os.path.splitext(thefilename)[1]
                    ext = ext.lower()
                    if ext !=".docm":
                        raise forms.ValidationError("文件类型不支持！请上传后缀为docm的文档！")
            elif self.word_mime_types is not None and self.word_mime_types == "text/plain":
                if word_file:
                    thefilename=word_file.name
                    ext = os.path.splitext(thefilename)[1]
                    ext = ext.lower()
                    if ext !=".txt":
                        raise forms.ValidationError("请上传txt文件！")
            elif self.word_mime_types is not None and self.word_mime_types == "application/zip":
                if word_file:
                    thefilename=word_file.name
                    ext = os.path.splitext(thefilename)[1]
                    ext = ext.lower()
                    if ext !=".zip":
                        raise forms.ValidationError("请压缩成zip文件后上传！")
        return word_file

    def clean_word_file(self):
        word_file = self.upload_word_file()
        if word_file is None:
            raise forms.ValidationError(_("This field is required."))
        
    def clean_check_file(self):
        check_file = self.cleaned_data['check_file']
        from django.template.defaultfilters import filesizeformat

        if check_file._size > self.check_max_file_size:
            raise forms.ValidationError(
                    _("Please keep file size under %(allowedsize)s. "
                    "Current filesize is %(uploadedsize)s.")
                    % {'allowedsize': filesizeformat(self.check_max_file_size),
                        'uploadedsize': filesizeformat(check_file._size)})

        if self.check_mime_types is not None and self.check_mime_types == "application/pdf":
            if check_file.read()[:4] != "%PDF":
                raise forms.ValidationError(_("Uploaded file is not a PDF."))
        elif self.check_mime_types is not None and self.check_mime_types == "application/vnd.ms-word.document.macroEnabled.12":
            if check_file:
                thefilename=check_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
                if ext !=".docm":
                    raise forms.ValidationError("文件类型不支持！请上传后缀为docm的文档！")
        elif self.check_mime_types is not None and self.check_mime_types == "text/plain":
            if check_file:
                thefilename=check_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
                if ext !=".txt":
                    raise forms.ValidationError("请上传txt文件！")
        elif self.check_mime_types is not None and self.check_mime_types == "application/zip":
            if check_file:
                thefilename=check_file.name
                ext = os.path.splitext(thefilename)[1]
                ext = ext.lower()
                if ext !=".zip":
                    raise forms.ValidationError("请压缩成zip文件后上传！")
                    
        try:
            string = check_file.read()
            truestring = true_string(string)
        except TypeError:
            raise forms.ValidationError("文件非法，请上传自动生成的校验文件！")
            
        soup = BeautifulSoup(truestring)
#        print self.institutional_id
        if not self.institutional_id and not self.is_course_staff:
            raise forms.ValidationError("请在您的个人信息中添加学号后再提交作业！")
        if idvalue(soup, 'email') <> self.institutional_id:
            if not self.is_course_staff:
                raise forms.ValidationError("学号不符，请确认上传的是你自己编辑的文档！")

        word_file = self.cleaned_data.get('word_file')

        if word_file:
            import hashlib 
            buf = word_file.read()
            md5string = hashlib.md5(buf).hexdigest()
            sha1string = hashlib.sha1(buf).hexdigest()

            if idvalue(soup, 'encrypt1') is not None:            
                if not (idvalue(soup, 'encrypt1') == md5string or idvalue(soup, 'encrypt2') == sha1string ):
                    raise forms.ValidationError("校验文件与上传的word文档不匹配！")
            else:
                raise forms.ValidationError(
                    "文档检查工具版本过低，请使用%s以上的版本生成校验文件！" % self.base_version
                )

            #print idvalue(soup, 'checkfile_version')

            if idvalue(soup, 'checkfile_version') is not None:
                if float(idvalue(soup, 'checkfile_version')) < self.base_version:
    #                print self.base_version
                    raise forms.ValidationError(
                        "文档检查工具版本过低，请使用%s以上的版本生成校验文件！" % self.base_version
                    )
            else:
                raise forms.ValidationError(
                    "文档检查工具版本过低，请使用%s以上的版本生成校验文件！" % self.base_version
                )

        return check_file

        
# vim: foldmethod=marker
